在前面章節我們已有進行模擬Spring框架之實作範例及介紹,使用者可理解到所有服務透過一項框架套件代理的過程,故本章節將提供使用者一些基本設置的套件,提供開發者了解Spring框架之控制反轉(IoC,Inverse of Control)與依賴注入(DI,Dependency Injection)兩項核心概念,運用此範例配置可提供給所有讀者當各類元件測試的基底,且讀者可快速學習到建立一套基礎的Spring Boot Starter服務架構需要運用到哪些套件與開發流程,而為什麼要選用Spring Boot Starter呢?因此套件的規範與設定優於Spring周遭套件,也整合許多外掛套件觀念,只需尋找spring-boot-starter-* 字母開頭套件當外掛,運用起來相當簡單,故在此推薦給眾多讀者做範例使用 !
implementation ('org.springframework.boot:spring-boot-starter')
implementation ('org.springframework.boot:spring-boot-starter-web')
compile('org.springframework.boot:spring-boot-starter-jetty')
compile('org.springframework.boot:spring-boot-starter-aop')
implementation group: 'com.google.code.gson', name: 'gson', version: '2.8.7'
//unit test
testCompile group: 'junit', name: 'junit', version: '4.12'
testCompile group: 'org.springframework', name: 'spring-test'
testCompile group: 'org.hamcrest', name: 'hamcrest-all', version : '1.3'
testCompile('org.springframework.boot:spring-boot-starter-test')
testCompile('org.springframework.security:spring-security-test')
本篇全部將以註解(Annotation)進行各種Bean的配置,故我們先產生一個基本的模型(Model named: Chef),透過AppConfig元件進行建立兩項Bean類別元件,當我們服務啟動的時候,會自動將Bean載入Spring IoC容器中,故我們亦可透過ApplicationContext方式取得Bean類別,亦可透過註解方式(@Autowired)獲取Bean類別,詳細範例如下請參照。
public static void main(String[] args) {
SpringApplication.run(ApplicationBoot.class,args);
}
Map<String,Object> beansContainer = null;
@PostConstruct
public void init() {
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
beansContainer = ctx.getBeansWithAnnotation(Bean.class);
}
@Override
public String getNameByChef(String name) {
return ((Chef)beansContainer.get(name)).getName();
}
main.java.sw.sample.spring.service.ConfigServiceImpl.java
@Primary
@Bean(name= "chef-B")
public Chef initChefB() {
Chef chef = new Chef();
long generatedLong = 500000L + (long) (Math.random() * (1000000L - 500000L));
chef.setId(generatedLong);
chef.setName("Jyuh");
chef.setRemark("Michelin-chef and dessert");
List<String> specialSkills = new ArrayList<String>();
specialSkills.add("Gold cake");
specialSkills.add("noodle pie");
specialSkills.add("chocolate pie");
chef.setSpecialSkills(specialSkills);
return chef;
}
test.java.sw.sample.spring.ConfigServiceSuite.java
@Autowired
Chef chef;
@Test
public void validateChefBRemark() {
assertTrue(chef.getRemark().equalsIgnoreCase("Michelin-chef and dessert"));
System.out.println("validate Chef B Remark success!");
}
透過以上程式碼區段敘述獲取Bean類別元件,便可進行各類程式邏輯運用與判斷。
在軟體開發週期定義上,無論何種元件都會有一個生命週期,以下是注入一個Bean的流程架構,Spring 會先取出相關屬性值,如:需註冊多個相同類別的Bean,將在每個Bean內建入不相同的變量參數,此時就會去對應不同的識別名稱去註冊Bean,此時會產生將配置給Bean的對映名稱,接下來進行確認此類別是否有配置初始化方法,若有會產生相關預設好的Bean,在進行觸發此方法進行初始化參數配置,最後將檢查是否有配置Destroy的方法,當進行系統關閉,每個Bean移除前都會觸發此方法,當配置完後會將此Bean存入BeanFactory變量池中,並將預設好的名稱作為索引值,以提供使用者隨時可呼叫配置使用。
圖一 Bean 生命週期
從此架構可以看出我們在範例中可以一次註冊多個Bean,每個Bean都可以連接@PostConstruct及@PreDestroy兩項方法,開發者可透過每個Bean的識別名稱進行獲取該元件類別,如何註冊多個Bean[chef-A、chef-B],請看下面代碼:
已註解方式註冊Bean
@Configuration
public class AppConfig {
@Bean(name= "chef-A")
public Chef initChefA() {
Chef chef = new Chef();
long generatedLong = 500000L + (long) (Math.random() * (1000000L - 500000L));
chef.setId(generatedLong);
chef.setName("Weisting");
chef.setRemark("Michelin-chef and main meal chef.");
List<String> specialSkills = new ArrayList<String>();
specialSkills.add("Gold chicken");
specialSkills.add("Assorted fried noodles");
specialSkills.add("Crab Fragrant Huangbao");
chef.setSpecialSkills(specialSkills);
return chef;
}
@Primary
@Bean(name= "chef-B")
public Chef initChefB() {
Chef chef = new Chef();
long generatedLong = 500000L + (long) (Math.random() * (1000000L - 500000L));
chef.setId(generatedLong);
chef.setName("Jyuh");
chef.setRemark("Michelin-chef and dessert");
List<String> specialSkills = new ArrayList<String>();
specialSkills.add("Gold cake");
specialSkills.add("noodle pie");
specialSkills.add("chocolate pie");
chef.setSpecialSkills(specialSkills);
return chef;
}
}
獲取Chef Bean 方法如下
public class ConfigServiceSuite extends ServiceTestBase {
@Autowired
ConfigService configService;
@Autowired
Chef chef;
@Test
public void validateChefBRemark() {
assertTrue(chef.getRemark().equalsIgnoreCase("Michelin-chef and dessert"));
System.out.println("validate Chef B Remark success!");
}
@Test
public void validateChefBName() {
assertTrue(chef.getName().equalsIgnoreCase("Jyuh"));
System.out.println("validate Chef B Remark success!");
}
...
}
public void validateChefBRemark() {
assertTrue(chef.getRemark().equalsIgnoreCase("Michelin-chef and dessert"));
System.out.println("validate Chef B Remark success!");
}
@Test
public void validateChefBName() {
assertTrue(chef.getName().equalsIgnoreCase("Jyuh"));
System.out.println("validate Chef B Remark success!");
}
Run test task
gradle test
Run open result html
open ./build/reports/tests/test/classes/sw.sample.spring.ConfigServiceSuite.html
Spring boot starter base sample code
Spring 容器中Bean生命周期之@PostConstruct和@PreDestroy註解
23.14 Using the @PostConstruct and @PreDestroy Annotations with CDI Managed Bean Classes